/*
 * Creta core takes control of events management, undo/redo history and selection code
 * Also it has part of shortcuts code.
 *
 * This code is under AGPLV3 by alkeon (alkeon@autistici.org).
 *
 */
  /*jslint browser: true*/

"use strict";

/*jshint esversion: 6 */
 
class creta_core {

	constructor(id, element) {
		this.id = id;
		this.element = element;
		this.canvas_history = [];
		this.s_history = true;
		this.cur_history_index = 0;
		this.DEBUG = false;
		this.delete_line = false;
		this.is_zooming = false;
		this.is_multiline = false;
	}
	
	// Main method for keydown events
	
	keydown_events(e){
		let key = e.key;
		if (key === 'Enter')
			this.set_enter_keydown(e);
			
		if (key === 'ArrowUp')
			this.set_arrow_up_keydown(e);
		
		if (key === 'ArrowDown')
			this.set_arrow_down_keydown(e);
		
		if(e.altKey){
			if (key === 'ArrowRight') {
			
				let line = this.get_selected_node_line();

				let selection = window.getSelection();

				let range = document.createRange();
				range.selectNodeContents(line);
				range.collapse(false);

				selection.removeAllRanges();
				selection.addRange(range);
				
				e.preventDefault();

			}
			if (key === 'ArrowLeft') {
			
				let line = this.get_selected_node_line();
				let selection = window.getSelection();
				let range = this.create_range(line, 0);
			
				if(range){
					range.collapse(false);
					selection.removeAllRanges();
					selection.addRange(range);
				}
				e.preventDefault();
			}  else if (key === 'i'){
				this.change_multiline_state();
				return false;
			}

		}else{

			if (key === 'ArrowLeft')
				this.set_arrow_left_character_keydown(e);
			
			if (key === 'ArrowRight' && !e.altKey)
				this.set_arrow_right_character_keydown(e);
			
		}
		if(e.ctrlKey){
			if (key === '4')
				this.set_nested_list_keydown(e);
			

			else if ((key === 'z') && !e.shiftKey){
				this.history_undo();
				return false;
			} else if ((key === 'Z') && e.shiftKey){
				this.history_redo();
				return false;
			}
		}
		if (key === 'Backspace')
			this.set_backspace_keydown(e);
		
	}
	
	/*
	 * Enter keydown method
	 * 
	 * It creates div with contenteditable activated when
	 * there aren't unordered lists
	 *
	 */
	set_enter_keydown(e){

		if(!e.target.innerHTML.includes("<ul")){
			e.preventDefault();
			//Get selection range and make it select from caret position to end of line.
			let line = this.get_selected_node_line();

			let editable_div = document.createElement('div');
			editable_div.setAttribute('contenteditable', 'true');
			
			if(window.getSelection().rangeCount > 0){
				let range = window.getSelection().getRangeAt(0);
				range.setEndAfter(line);

				//Get all nodes from selection range
				let selectedFragment = range.cloneContents();
				
				if(selectedFragment.childNodes.length > 0){
				
					//Split div into two divs when isn't an embed div nor table data
					if(selectedFragment.childNodes[0].className != "embed" && !e.target.innerHTML.includes("<td")) {
						//Append every node that isn't a div with contenteditable enabled
						for(let e = 0; e < selectedFragment.childNodes.length; ++e) {
							if(typeof selectedFragment.childNodes[e].childNodes !== "undefined") {
								if(selectedFragment.childNodes[e].textContent != ""){
									if(selectedFragment.childNodes[e].tagName == "DIV" &&
										selectedFragment.childNodes[e].contentEditable == "true")

										for(let i = 0; i < selectedFragment.childNodes[e].childNodes.length; ++i)
											editable_div.appendChild(
												selectedFragment.childNodes[e].childNodes[i].cloneNode(true));
									
									else
										editable_div.appendChild(selectedFragment.childNodes[e].cloneNode(true));
								}
							} else
								editable_div.appendChild(selectedFragment.childNodes.cloneNode(true));
						}
						
						//Delete cloned range
						range.deleteContents();
					}
				}
			}
			
			e.target.insertAdjacentElement("afterend", editable_div);
			editable_div.focus();

			this.save_history();
			this.save();
			
		}else{
			
			let text = this.get_selected_node();
			if(text.innerHTML == "<br>"){

				let editable_div = document.createElement('div');
				editable_div.setAttribute('contenteditable', 'true');
				if(text.nodeName == 'LI')
					text.remove();
				
				e.target.insertAdjacentElement("afterend", editable_div);

				editable_div.focus();
				e.preventDefault();
				this.save_history();
				this.save();
			}else{
				this.save_history();
			}
		}
		
	}

	/*
	 * Alt + I keydown method
	 *
	 * Enable/disable multiline mode
	 *
	 */
	change_multiline_state(){
		if(this.is_multiline){
			this.element.getElementsByClassName('visual-editor')[0].contentEditable = 'false';
		}else{
			this.element.getElementsByClassName('visual-editor')[0].contentEditable = 'true';
		}
	}
	
	/*
	 * Enter keyup method
	 * 
	 * Add new checkitem listener to list
	 *
	 */
	set_enter_keyup(e){
		if(e.target.innerHTML.includes("<ul")){
			this.set_checkitem_element_listeners();
		}

	}
	
	/*
	 * Arrow Up keydown method
	 * 
	 * It moves to upper div unless previous element is a list.
	 * List behaviour is the browser default one
	 */
	set_arrow_up_keydown(e){
		let previous_element = e.target.previousElementSibling;
		if(previous_element != null){
			if(previous_element.nodeName == "BR"){
			
				previous_element = previous_element.nextElementSibling;
				previous_element.focus();
				e.preventDefault();
				
			}else if(previous_element.children[0] != undefined && 
				previous_element.children[0].nodeName == "UL" && e.target.nodeName == "DIV"){
				
				previous_element = this.get_last_nested_list_item(previous_element);
				let selection = window.getSelection();
				selection.removeAllRanges();

				let range = document.createRange();
				range.selectNode(previous_element);
				range.collapse(true);
				selection.addRange(range);
				e.preventDefault();
				
			}else if(!e.target.innerHTML.includes("<ul")){

				let line = this.get_selected_node_line();
				let position = this.get_caret_character_offset(line);
				if(position > previous_element.textContent.length)
					position = previous_element.textContent.length;
					
				let range = this.create_range(previous_element, position);
				if(range){
					setTimeout(function() {
						previous_element.focus();
						let selection = window.getSelection();
						range.collapse(false);
						selection.removeAllRanges();
						selection.addRange(range);
					}, 0);
				}
				e.preventDefault();
			}else{

				let text = this.get_selected_node();
				if(text == e.target.children[0].children[0]){
					setTimeout(function() {
						previous_element.focus();
					}, 0);

				}
			}
		}
	}
	
	/*
	 * Arrow Down keydown method
	 * 
	 * It moves to lower div unless next element is a list.
	 *
	 */
	set_arrow_down_keydown(e){
		let next_element = e.target.nextElementSibling;
		if(next_element != null){
			if(!e.target.innerHTML.includes("<ul")){
				if(next_element.nextElementSibling != null && next_element.nodeName == "BR"){
					next_element = next_element.nextElementSibling;
				}

				let line = this.get_selected_node_line();
				let position = this.get_caret_character_offset(line);
				if(position > next_element.textContent.length)
					position = next_element.textContent.length;
				let range = this.create_range(next_element, position);
				if(range){
					setTimeout(function() {
						next_element.focus();
						let selection = window.getSelection();
						range.collapse(false);
						selection.removeAllRanges();
						selection.addRange(range);
					}, 0);
				}
				e.preventDefault();
			}else{
				let text = this.get_selected_node();

				if(this.is_last_list_item(text)){
					next_element.focus();
				}
			}
		}
	}
	
	/*
	 * Arrow Left keydown method
	 * 
	 * It moves to lower div unless next element is a list.
	 *
	 */
	set_arrow_left_character_keydown(e){
		let previous_element = e.target.previousElementSibling;
		if(previous_element != null){
			let line = this.get_selected_node_line();
			let position = this.get_caret_character_offset(line);
			if(position == 0 && previous_element.contentEditable == 'true' && previous_element.nodeName == "DIV"){
				if(!e.target.outerHTML.includes("class=\"embed\"") || e.target.innerHTML.includes("class=\"embed\"")){
					let textLength = this.get_last_offset(previous_element);

					let selection = window.getSelection();
					if(textLength == 1)
						textLength = 0;

					let range = this.create_range(previous_element, textLength);
				
					if(range){
						range.collapse(false);
						selection.removeAllRanges();
						selection.addRange(range);
					}
					e.preventDefault();
				}
			}
		}
	}
	
	/*
	 * Arrow Right keydown method
	 * 
	 * It moves to lower div unless next element is a list.
	 *
	 */
	set_arrow_right_character_keydown(e){
		let next_element = e.target.nextElementSibling;
		if(next_element != null){
			let line = this.get_selected_node_line();
			let position = this.get_caret_character_offset(line);
			if(position == this.get_last_offset(line) && next_element.contentEditable == 'true'){
				if(!e.target.outerHTML.includes("class=\"embed\"") || e.target.innerHTML.includes("class=\"embed\"")){
					let selection = window.getSelection();

					let range = this.create_range(next_element, 0);
				
					if (range) {
						range.collapse(false);
						selection.removeAllRanges();
						selection.addRange(range);
					}
					e.preventDefault();
				}
			}
		}
	}
	
	/*
	 * Sublist keydown method
	 * 
	 * Insert sublist in list
	 *
	 */
	set_nested_list_keydown(e){
		if(e.target.innerHTML.includes("<ul")){

			let ul = document.createElement('ul');

			let li = document.createElement('li');

			ul.appendChild(li);
				
			let text = this.get_selected_node();
			text.appendChild(ul);
			let selection = window.getSelection();
			selection.removeAllRanges();

			let range = document.createRange();
			range.selectNodeContents(li);
			range.collapse(true);
			selection.addRange(range);
			if(!this.is_scrolled_into_view(li))
				li.scrollIntoView();
			e.preventDefault();
			this.save_history();
			this.save();
		}
	}
	
	/*
	 * Backspace keydown method
	 * 
	 * Delete line if it there isn't text
	 *  -	Checks deleting characters in embed divs
	 *  -	Checks deleting characters list and nested lists
	 */
	set_backspace_keydown(e){
		if(e.target.innerHTML == "<br>" || e.target.innerHTML == "" || /^[ ]+$/.test(e.target.innerHTML) || /^[\n]+[ ]*[\n]*$/.test(e.target.innerHTML) || e.target.innerHTML == '\n    <div class="embed"></div>\n' || e.target.innerHTML == '<ul><li><br></li></ul>'){
			let prevElement = e.target.previousElementSibling;
			if(prevElement.children[0] != undefined && prevElement.children[0].nodeName == "UL" && e.target.nodeName == "DIV"){
				prevElement = this.get_last_nested_list_item(prevElement);

				let selection = window.getSelection();
				selection.removeAllRanges();

				let range = document.createRange();
				range.selectNode(prevElement);
				range.collapse(true);
				selection.addRange(range);
				e.target.remove();
				e.preventDefault();
			}else{

				e.target.remove();
				let textLength = 0;
				if(prevElement != null){
					textLength = prevElement.innerText.length;
					let selection = window.getSelection();

					let range = this.create_range(prevElement, textLength);
				
					if (range) {
						range.collapse(false);
						selection.removeAllRanges();
						selection.addRange(range);
					}
				}
				
				e.preventDefault();
			}

		}else{
			let text = this.get_selected_node();
			if(typeof text !== "undefined" && text.innerHTML == "<br>" && !e.target.innerHTML.includes("<li") && !e.target.innerHTML.includes("<tr")){

				let editable_div = document.createElement('div');
				editable_div.setAttribute('contenteditable', 'true');

				e.target.insertAdjacentElement("beforebegin", editable_div);
				text.remove();
				editable_div.focus();
				e.preventDefault();
				this.save_history();
				this.save();
			}else if(e.target.innerHTML.includes("<li")){
				let selection = window.getSelection();
				if(selection != null && selection.anchorNode != null){
					if(selection.anchorNode == e.target.children[0].children[0]){
						let previousElement = e.target.previousElementSibling;
						previousElement.target.remove();
						let textLength = 0;
						if(previousElement != null){
							textLength = previousElement.innerText.length;
							selection = window.getSelection();

							let range = this.create_range(previousElement, textLength);
						
							if(range){
								range.collapse(false);
								selection.removeAllRanges();
								selection.addRange(range);
							}
						}
						
						e.preventDefault();
					}
				}	
			}else{
				let line = e.target;

				let position = this.get_caret_character_offset(line);
				let prevElement = e.target.previousElementSibling;
				if(position == 0 && prevElement != null && prevElement.contentEditable == 'true' && prevElement.nodeName == "DIV" && !prevElement.outerHTML.includes("class=\"embed\"") && !line.outerHTML.includes("class=\"embed\"") ){
					let textLength = prevElement.innerText.length;

					prevElement.innerText += line.innerText;
					let selection = window.getSelection();
					let range = this.create_range(prevElement, textLength);
					if (range) {
						range.collapse(false);
						selection.removeAllRanges();
						selection.addRange(range);
					}
					line.remove();
					e.preventDefault();
				}
			}
		}
	}
	
	/*
	 * Insert link with href
	 * Use selected line in editor when possible
	 * 
	 */
	create_link(href) {
		
		let a = document.createElement('a');
		a.setAttribute("href", href);
			
		if(range_link.toString() == ""){

			a.append(document.createTextNode(href));

			selected_for_visual_line.append(a);
			
		}else{
			
			a.append(document.createTextNode(range_link.toString()));

			document.getSelection().removeAllRanges();
			document.getSelection().addRange(range_link);

			range_link.deleteContents();
			range_link.insertNode(a);
			
		}
		
		if(!this.is_scrolled_into_view(a))
			a.scrollIntoView();
		
		this.save_history();
	}
	
	/*
	* Insert table in selected line if there is 
	* w: positive number between 1 and 20 that defines table columns
	* h: positive number between 1 and 20 that defines table rows
	*
	*/
	create_table(w,h) {

		if(!Number.isNaN(w) && !Number.isNaN(h)){
			if(w < 20 && w > 0 && h > 0 && h < 20){
				var tbl = document.createElement('table');
				tbl.contentEditable = 'true';

				var div = document.createElement('div');
				div.className = "deletebutton editor-div";
				div.contentEditable = "false";
				let editor = this;
				div.onclick = editor.delete_element;
				div.appendChild(document.createTextNode('🗙'));

				var newline = document.createElement('div');
				newline.contentEditable = 'true';

				for(var i = 0; i < h; i++){
					var tr = tbl.insertRow();
					for(var j = 0; j < w; j++){
						var td = tr.insertCell();
						td.appendChild(document.createElement('br'));
					}
				}

				selected_for_visual.insertAdjacentElement("afterend", newline);
				selected_for_visual.insertAdjacentElement("afterend", tbl);
				selected_for_visual.insertAdjacentElement("afterend", div);
				if(!this.is_scrolled_into_view(div))
					div.scrollIntoView();
				newline.focus();
				this.save_history();
				return true;
			}
		}
		return false;
		
	}
	
	/*
	 * Save editor HTML into indexedDB defined by editor id.
	 * 
	 */
	save(){
		try{
		
			var core = this;
			// Open (or create) the database
			let open = window.indexedDB.open("nibis", 1);

			// Create the schema
			open.onupgradeneeded = function() {
				let db = open.result;
				let store = db.createObjectStore("editor", {keyPath: "id"});
				let index = store.createIndex("id", "text");
			};

			open.onsuccess = function() {
				// Start a new transaction
				let db = open.result;
				let tx = db.transaction("editor", "readwrite");
				let store = tx.objectStore("editor");

				// Add some data
				store.put({id: core.id, text: core.element.getElementsByClassName('visual-editor')[0].innerHTML});

				// Close the db when the transaction is done
				tx.oncomplete = function() {
					db.close();
				};
			}
		}catch(e){
			console.log("Couldn't save history " + e);
		}
	}
	
	/*
	 * Load editor from indexedDB and by editor id
	 * After this, set listeners for every table and image
	 */
	load(){
		try{

			var core = this;
			// Open (or create) the database
			let open = window.indexedDB.open("nibis", 1);

			// Create the schema
			open.onupgradeneeded = function() {
				let db = open.result;
				let store = db.createObjectStore("editor", {keyPath: "id"});
				let index = store.createIndex("id", "text");
			};

			open.onsuccess = function() {
				// Start a new transaction
				let db = open.result;
				let tx = db.transaction("editor", "readwrite");
				let store = tx.objectStore("editor");
				
				// Query the data
				var entry = store.get(core.id);

				entry.onsuccess = function() {
					try{
						core.element.getElementsByClassName('visual-editor')[0].innerHTML = entry.result.text.replace(/ /g, " ");
						core.set_delete_element_listeners();
						core.set_zoom_image_listeners();
						core.set_checkitem_element_listeners();
					}catch(e){
						console.log('editor data not found');
					}
				};

				// Close the db when the transaction is done
				tx.oncomplete = function() {
					db.close();
				};
			}
		}catch(e){
			console.log("Couldn't load history " + e);
		}
	}
	
	/*
	 * Insert image in selected position in editor with value alt set
	 *
	 */
	create_image(alt) {
		var file = document.querySelector(".image_toolbar").files[0];

		var reader = new FileReader();

		let dataURI;
		let editor = this;
		reader.addEventListener(
			"load",
			function() {
				dataURI = reader.result;
				
				let content_editable_div = document.createElement('div');

				let img = document.createElement("img");
				img.src = dataURI;
				img.alt = alt;
				
				var div = document.createElement('div');
				div.className = "deletebutton editor-div";
				div.contentEditable = "false";
				div.onclick = editor.delete_element;
				div.appendChild(document.createTextNode('🗙'));

				let image_div = document.createElement('div');
				image_div.className = "image editor-div";
				image_div.appendChild(img);

				selected_for_visual.insertAdjacentElement("afterend", content_editable_div);
				selected_for_visual.insertAdjacentElement("afterend", image_div);
				selected_for_visual.insertAdjacentElement("afterend", div);

				if(!editor.is_scrolled_into_view(image_div))
					image_div.scrollIntoView();

				content_editable_div.focus();
				editor.save_history();
				document.querySelector(".image_toolbar").value = null;
				editor.set_zoom_image_listener(image_div);
			},
			false
		);

		if(file)
			reader.readAsDataURL(file);

	}
	
	/*
	 * Event method that deletes table from editor
	 *
	 */
	delete_element(e) {
		var inp = e.target;
		if(inp != null) {
			
			if(inp.previousElementSibling != null)
				inp.previousElementSibling.focus();
		
			if(inp.nextElementSibling != null)
				inp.nextElementSibling.remove();
			
			inp.remove();
		}
	}
	
	/*
	 * Event method that zoom image from editor
	 *
	 */
	zoom_image(e) {
		let src_element = e.srcElement;
		if(src_element.tagName == "IMG")
			src_element = src_element.parentElement;
		if(this.is_zooming){
			this.is_zooming = false;

			src_element.style.removeProperty("margin-top");
			src_element.style.removeProperty("height");
			if(src_element.previousElementSibling != null){
				src_element.previousElementSibling.style.removeProperty("display");
				src_element.previousElementSibling.removeAttribute("style");
			}
			src_element.removeAttribute("style");
			let nextElement = src_element.parentElement.nextElementSibling;
			let previousElement = src_element.parentElement.previousElementSibling;
			while(nextElement) {
				nextElement.style.removeProperty("display");
				nextElement.removeAttribute("style");
				nextElement = nextElement.nextElementSibling;
			}
			while(previousElement) {
				previousElement.style.removeProperty("display");
				previousElement.removeAttribute("style");
				previousElement = previousElement.previousElementSibling;
			}
			src_element.style.transform = '';
		}else{
			this.is_zooming = true;
			
			src_element.style.marginTop = '150px';
			src_element.style.height = '500px';
			if(src_element.previousElementSibling != null)
				src_element.previousElementSibling.style.setProperty("display", "none");
			let nextElement = src_element.parentElement.nextElementSibling;
			let previousElement = src_element.parentElement.previousElementSibling;
			while(nextElement) {
				nextElement.style.setProperty("display", "none");
				nextElement = nextElement.nextElementSibling;
			}
			while(previousElement) {
				previousElement.style.setProperty("display", "none");
				previousElement = previousElement.previousElementSibling;
			}
			src_element.style.transform = 'scale(2)';
		}

	}
	
	/*
	 * Insert block in selected line if there is 
	 * class_name: define class name
	 *
	 */
	create_block(class_name) {

		let div = document.createElement('div');
		const previousContent = document.createTextNode(range_block.toString());

		document.getSelection().removeAllRanges();
		document.getSelection().addRange(range_block);

		div.setAttribute("class", class_name);
		if(class_name == "checkitem" || class_name.includes("checkitem "))
			this.set_checkitem_element_listener(div);
		
		div.append(previousContent);

		range_block.deleteContents();
		range_block.insertNode(div);

		this.save_history();

	}
	
	/*
	 * Check if element is an editor line
	 * element: define element in DOM
	 *
	 */
	is_element_editor_line(element){
		return (element.tagName == "DIV" && element.contentEditable == "true");
	}
	
	/*
	 * Check if element is an heading tag
	 * element: define element in DOM
	 *
	 */
	is_element_heading(element){
		return (element.tagName == "H1" || element.tagName == "H2" || element.tagName == "H3" || element.tagName == "H4");
	}
	
	/*
	 * Check if element is embed line type
	 * element: define element in DOM
	 *
	 */
	is_element_embed(element){
		return (element.className == "embed");
	}
	
	/*
	 * Delete heading or embed tags in selected line from DOM.
	 * if an editor line is found, check its children
	 * if a tag is found, check its parent
	 * when a heading or a embed is found deletes it
	 */
	clear_line_format(){
		let text = this.get_selected_node();
		const previousContent = document.createTextNode(text.innerText);
		if(this.is_element_embed(text) || !this.is_element_editor_line(text)){
			let node = text;
			while(!this.is_element_editor_line(node) && !this.is_element_heading(node) && node.tagName != "BODY"){
				node = node.parentNode;
			}
			if(this.is_element_heading(node)){
				node.parentNode.appendChild(previousContent);
				node.remove();
			}
			if(this.is_element_embed(node)){
				node.classList.remove("embed");
				node.removeAttribute("class");
			}
		} else {
			if(text.children[0] != null){
				if(this.is_element_heading(text.children[0])){
					text.appendChild(previousContent);
					text.children[0].remove();
				}
			} else {
				alert("Please put cursor on editor.");
			}
		}

		this.save_history();
	}
	
	/*
	 * Insert command in selected line
	 * command: h1, h2, h3
	 *
	 */
	change_line_format(command){

		let text = this.get_selected_node_line();

		const previousContent = document.createTextNode(text.innerText);
		text.innerHTML = "";
		
		let link = document.createElement(command);

		link.append(previousContent);
		
		text.appendChild(link);
		this.save_history();
	}
	
	/*
	 * Insert list in selected line if there is 
	 *
	 */
	create_list(){
		var text = this.get_selected_node_line();
		var div = document.createElement('div');
		var li = document.createElement('li');
		var ul = document.createElement('ul');

		div.contentEditable = 'true';
		ul.appendChild(li);
		div.appendChild(ul);
		text.insertAdjacentElement('afterend', div);
		
		if(!this.is_scrolled_into_view(li))
			li.scrollIntoView();

		let range = document.createRange();
			
		range.selectNodeContents(li);

		document.getSelection().removeAllRanges();
		document.getSelection().addRange(range);
		this.save_history();

	}
	
	/*
	 * Insert selected text in code tag
	 *
	 */
	create_code(command = 'code'){
		if(document.getSelection().toString().length != 0) {
			
			let range = this.get_selected_node();
			if(range.innerHTML == document.getSelection().toString()) {
				let new_element = document.createElement(command);
				new_element.append(document.createTextNode(range.innerText));
				range.innerHTML = "";
				range.appendChild(new_element);

			} else {
			
				let range = window.getSelection().getRangeAt(0);
				let new_element = document.createElement(command);
				new_element.append(document.createTextNode(range.toString()));

				range.deleteContents();
				range.insertNode(new_element);

			}
			this.save_history();
		 } else
			alert("Please select some text before editing the content.");
	}

	/*
	 * Append undo/redo history in array
	 * - Delete forward history when cur_history_index is previous than last position
	 * - Append current state if it is different for cur_history_index one
	 *
	 */
	save_history(){

		if(this.cur_history_index < this.canvas_history.length - 1){
			this.canvas_history = this.canvas_history.slice(0, this.cur_history_index + 1);
			this.cur_history_index++;
		}
		
		var cur_canvas = this.element.getElementsByClassName('visual-editor')[0].innerHTML;
		if(cur_canvas != this.canvas_history[this.cur_history_index]){
			this.canvas_history.push(cur_canvas);
			this.cur_history_index = this.canvas_history.length - 1;
		}
		this.log_text('saved ' + this.canvas_history.length + " " + this.cur_history_index);
	}

	/*
	 * Undo history when current_index is more than 0
	 * - Delete forward history when cur_history_index is previous than last position
	 * - Append current state if it is different for cur_history_index one
	 *
	 */
	history_undo(target){
		if(this.cur_history_index > 0){
			this.s_history = false;
			var canv_data = this.canvas_history[this.cur_history_index - 1];
			let editor = this.element.getElementsByClassName('visual-editor')[0];
			if(editor != undefined){ 
				var index_id =  this.get_index_from_node(document.activeElement);
				this.element.getElementsByClassName('visual-editor')[0].innerHTML = canv_data;
				editor = this.element.getElementsByClassName('visual-editor')[0];
				if(index_id > 0 && this.element.getElementsByClassName('visual-editor')[0].children.length - 1 > index_id){
					setTimeout(function() {
						editor.children[index_id].focus();
					}, 0);

				}else{
					setTimeout(function() {
						editor.lastElementChild.focus();
					}, 0);
				}
			}else{
				editor.innerHTML = canv_data;
			}
			
			this.cur_history_index--;
			this.log_text('undo ' + this.canvas_history.length + " " + this.cur_history_index);
		}
		this.set_delete_element_listeners();
		this.set_zoom_image_listeners();
		this.set_checkitem_element_listeners();
	}
	
	/*
	 * Redo history when current_index is more than 0 and 
	 * - Delete forward history when cur_history_index position in array is defined
	 * - Append current state if it is different for cur_history_index one
	 *
	 */
	history_redo(){
		if(this.canvas_history[this.cur_history_index]){
			this.s_history = false;
			var canv_data = this.canvas_history[this.cur_history_index];
			let editor = this.element.getElementsByClassName('visual-editor')[0];
			if(editor != undefined){ 
				var index_id =  this.get_index_from_node(document.activeElement);
				editor.innerHTML = canv_data;
				if(index_id + 1 < editor.children.length - 1)
					index_id++;
				setTimeout(function() {
					editor.children[index_id].focus();
				}, 0);

			}else{
				editor.innerHTML = canv_data;
			}

			this.cur_history_index++;
			this.log_text('redo ' + this.canvas_history.length + " " + this.cur_history_index);
		}
		this.set_delete_element_listeners();
		this.set_zoom_image_listeners();
		this.set_checkitem_element_listeners();
	}
	
	/*
	 * Get node position in parent node children list.
	 * 
	 */
	get_index_from_node(node) {
	    var children = node.parentNode.children;
	    var num = 0;
	    for (var i = 0; i < children.length; i++) {
		 if (children[i] == node) return num;
		 if (children[i].nodeType == 1) num++;
	    }
	    return -1;
	}
	
	/*
	 * Set delete listeners for all images and tables
	 *
	 */
	set_delete_element_listeners(){
		let x = document.getElementsByClassName("deletebutton");
		let array_length = x.length;
		var this_object = this;
		for (let i = 0; i < array_length; i++) {
			x[i].addEventListener("click", function (e){
				this_object.delete_element(e);
				this_object.save_history();
			});
		}
	}
	
	/*
	 * Set zoom listener for received element
	 *
	 *
	 */
	set_zoom_image_listener(e) {
		var this_object = this;
		e.addEventListener("click", function(e) {
			this_object.zoom_image(e);
			this_object.save_history();
		});
	}
	
	/*
	 * Set zoom listeners for all images
	 *
	 */
	set_zoom_image_listeners(){
		let x = document.getElementsByClassName("image");
		let array_length = x.length;

		for (let i = 0; i < array_length; i++) {
			this.set_zoom_image_listener(x[i]);
		}
	}
	
	/*
	 * Set checkitem for received element
	 *
	 *
	 */
	set_checkitem_element(e) {
		if(typeof e.target !== 'undefined')
			e.target.classList.toggle("cross-out");
		else
			e.classList.toggle("cross-out");
	}
	
	/*
	 * Set checkitem for received element listener
	 *
	 *
	 */
	set_checkitem_element_listener(e) {
		e.addEventListener("click", this.set_checkitem_element);
		this.save_history();
	}
	
	/*
	 * Set checkitem listeners for all divs
	 *
	 */
	set_checkitem_element_listeners(){
		let x = document.getElementsByClassName("checkitem");
		let array_length = x.length;
		var this_object = this;
		for (let i = 0; i < array_length; i++) {
			x[i].removeEventListener("click", this.set_checkitem_element);
			this_object.set_checkitem_element_listener(x[i]);
		}
	}
	
	/*
	 * Create range with specific values
	 * - node: element where range will be created
	 * - chars: number of chars selected in node
	 * - range: use existing range
	 *
	 */
	create_range(node, count, range) {
		if(!range) {
			range = document.createRange();
			range.selectNode(node);
			range.setStart(node, 0);
		}

		if(count === 0)
			range.setEnd(node, count);
		else if(node && count > 0) {
			if(node.nodeType === Node.TEXT_NODE) {
				if(node.textContent.length < count)
					count -= node.textContent.length;
				else {
					range.setEnd(node, count);
					count = 0;
				}
			} else {
				for (var lp = 0; lp < node.childNodes.length; lp++) {
					range = this.create_range(node.childNodes[lp], count, range);

					if(count === 0) break;
					
				}
			}
		} 

		return range;
	}
	
	
	/*
	 * Get last item from node parameter.
	 * - node: list element in a div
	 * Ex:
	 * - item1
	 * - item2
	 * 	* item3
	 * - item4
	 * 	* item5
	 * 		- item6 <---- get this list item
	 */
	get_last_nested_list_item(node){
		node = node.children[0];
		
		while(!this.is_last_list_item(node)){
			if(node.tagName.toLowerCase() == 'div'){
				if(node.children[0] != undefined){
					node = node.children[0];
				}

			}
			if(node.tagName.toLowerCase() == 'ul'){
				if(node.lastElementChild != undefined){
					node = node.lastElementChild;
				}
			}
			if(node.tagName.toLowerCase() == 'li'){
				if(node.lastElementChild != undefined){
					node = node.lastElementChild;
				}
			}
		}
		return node;
	}

	/*
	 * Check if node is leaf node from list tree.
	 *
	 */
	is_last_list_item(node){
		if(node.nextElementSibling == null && ((node.children.length == 1 && node.children[0].tagName == "BR") ||(node.children.length == 0))){
			return true;
		}
		return false;
	}
	
	/*
	 * Get caret position from selected element.
	 * - Usually caret position is the number of chars before caret position
	 */
	get_caret_character_offset(element) {
		let caret_offset = 0;
		if (typeof window.getSelection != "undefined") {
			let range = window.getSelection().getRangeAt(0);
			let pre_caret_range = range.cloneRange();
			pre_caret_range.selectNodeContents(element);
			pre_caret_range.setEnd(range.endContainer, range.endOffset);
			caret_offset = pre_caret_range.toString().length;
		} else if (typeof document.selection != "undefined" && document.selection.type != "Control") {
			let text_range = document.selection.createRange();
			let pre_caret_text_range = document.body.createTextRange();
			pre_caret_text_range.moveToElementText(element);
			pre_caret_text_range.setEndPoint("EndToEnd", text_range);
			caret_offset = pre_caret_text_range.text.length;
		}
		return caret_offset;
	}
	
	/*
	 * Get last position from selected element.
	 *
	 */
	get_last_offset(element) {
		var caretOffset = 0;
		if (typeof window.getSelection != "undefined") {

			var preCaretRange = document.createRange();
			preCaretRange.selectNodeContents(element);
			caretOffset = preCaretRange.toString().length;
		}
		return caretOffset;
	}
	
	/*
	 * Methods that checks event for list shortcut
	 * Ctrl + 1/2/3
	 *
	 */
	heading_keybinding(e){

		if((e.key == '1') && e.ctrlKey) {
			this.change_line_format('h1');
			e.preventDefault();
			return false;
		}

		if((e.key == '2') && e.ctrlKey) {
			this.change_line_format('h2');
			e.preventDefault();
			return false;
		}

		if((e.key == '3') && e.ctrlKey) {
			this.change_line_format('h3');
			e.preventDefault();
			return false;
		}
	}
	
	/*
	 * Methods that checks event for list shortcut
	 * Ctrl + Shift + L
	 *
	 */
	list_keybinding(e){

		if(e.key == 'L' && e.ctrlKey) {
			selected_for_visual = this.get_selected_node_line();

			this.element.getElementsByClassName('list_toolbar')[0].click();
			event.preventDefault();
			return false;
		}
	}
	
	/*
	 * Methods that checks event for code shortcut
	 * Ctrl + Shift + M
	 *
	 */
	code_keybinding(e){

		if((e.key == 'M') && e.ctrlKey) {
			this.create_code();
			e.preventDefault();
			return false;
		}
	}
	
	/*
	 * Log text if DEBUG is activated
	 *
	 */
	log_text(line){
		if(this.DEBUG)
			console.log(line);

	}
	
	/*
	 * Get selected node given by getSelection
	 * returns undefined if not selected
	 *
	 */
	get_selected_node(get_line = false) {
		var node, selection;
		if (window.getSelection) {
			selection = window.getSelection();
			node = selection.anchorNode;
		}
		if (!node && document.selection) {
			selection = document.selection;
			var range = selection.getRangeAt ? selection.getRangeAt(0) : selection.createRange();
			node = range.commonAncestorContainer ? range.commonAncestorContainer :
				range.parentElement ? range.parentElement() : range.item(0);
		}

		if (node) {
			if(get_line){
				while((node.nodeName != "DIV" || node.parentNode.nodeName != "DIV" || node.className != "" || !node.parentNode.className.includes("overflow-content")) && node.parentNode != null){
					node = node.parentNode;

				}
				if(node != null && node.parentNode != null){
					return (node.nodeName == "#text" ? node.parentNode : node);
				}
			
			}else{
				this.log_text("test " + node);
				return (node.nodeName == "#text" ? node.parentNode : node);
			}
		}
	}
	
	/*
	 * Get parent node from selected node given by getSelection
	 *
	 */
	get_selected_node_line() {
		var node = this.get_selected_node(true);
		if(node == null || node.parentNode == null){
			return document.querySelector(".visual-editor").lastElementChild;
		}else{
			return node;
		}
	}
	
	/*
	 * Deletes editor content and select first div
	 * 
	 */
	clean(e){
		e.currentTarget.sourceTarget.getElementsByClassName('visual-editor')[0].innerHTML = '<div contenteditable="true"></div>';
		this.save_history();
		var event = e.currentTarget.sourceTarget;
		setTimeout(function() {

			let range = document.createRange();
			let referenceNode = event.getElementsByClassName('visual-editor')[0].children[0];
			referenceNode.scrollIntoView();
			range.selectNodeContents(referenceNode);

			document.getSelection().removeAllRanges();
			document.getSelection().addRange(range);
		}, 0);
	}
	
	/*
	 * Open link popup, get link range and set focus on link input
	 * 
	 */
	link_toolbar(e){
		selected_for_visual = this.get_selected_node();
		selected_for_visual_line = this.get_selected_node_line();
		e.currentTarget.sourceTarget.getElementsByClassName('link_popup')[0].style.display = "block";
		range_link = window.getSelection().getRangeAt(0);
		e.currentTarget.sourceTarget.getElementsByClassName('link_input')[0].focus();
	}
	
	/*
	 * Open table popup and set focus on width input
	 * 
	 */
	table_toolbar(e){
		selected_for_visual = this.get_selected_node_line();
		e.currentTarget.sourceTarget.getElementsByClassName('table_popup')[0].style.display = "block";
		e.currentTarget.sourceTarget.getElementsByClassName('h')[0].focus();
		return false;
	}
	
	/*
	 * Open image popup range and set focus on alt input
	 * 
	 */
	image_toolbar(e){
		selected_for_visual = this.get_selected_node_line();
		e.currentTarget.sourceTarget.getElementsByClassName('image_popup')[0].style.display = "block";
		e.currentTarget.sourceTarget.getElementsByClassName('alt_input')[0].focus();
		return false;
	}
	
	/*
	 * Set selected node line as embed div or create one
	 * 
	 */
	embed_toolbar(e){
		var text = this.get_selected_node_line();

		if(text.innerHTML.includes("<table") || text.innerHTML.includes("<button") || text.innerHTML.includes("<img")){
			text = document.createElement('div');
			text.contentEditable = 'true';
			document.querySelector('.visual-editor').appendChild(text);
		}

		text.setAttribute("class", "embed");
		if(!this.is_scrolled_into_view(text))
			text.scrollIntoView();
		this.save_history();
	}
	
	/*
	 * Check if element is on screen
	 * 
	 * 
	 */
	is_scrolled_into_view(elem) {
		var view_top = window.scrollTop;
		var view_bottom = view_top + window.height;
		var elem_top = elem.offsetTop;
		var elem_bottom = elem_top + elem.height;
		return ((elem_bottom <= view_bottom) && (elem_top >= view_top));
	}
	
	/*
	 * Paste event handling 
	 * when copied text is pasted it checks for new lines (\n) if there's add it to new line.
	 *
	 */
	 paste(e) {
		let paste = (e.clipboardData || window.clipboardData || e.originalEvent.clipboardData).getData('text');
		paste = paste.replace( /(<([^>]+)>)/ig, '');
		let new_line_pos = paste.search("\n");
		if(new_line_pos == -1) {
			let selection = window.getSelection();
			if (!selection.rangeCount) return false;
			if(!e.target.className.includes("visual-editor")){

				selection.deleteFromDocument();
				let textNode = document.createTextNode(paste);
				selection.getRangeAt(0).insertNode(textNode);
				selection.removeAllRanges();
				
				let range = document.createRange();
				range.selectNode(textNode);
				selection.addRange(range);
				selection.collapseToEnd();
			}
		}else{

			while(new_line_pos != -1){
				const selection = window.getSelection();
				if (!selection.rangeCount) return false;
				if(!e.target.className.includes("visual-editor")){

					selection.deleteFromDocument();
					let textNode = "";
					if(new_line_pos != -1)
						textNode = document.createTextNode(paste.substring(0, new_line_pos));
					else
						textNode = document.createTextNode(paste.substring(0));
					selection.getRangeAt(0).insertNode(textNode);
					selection.removeAllRanges();
					
					let range = document.createRange();
					range.selectNode(textNode);
					selection.addRange(range);
					selection.collapseToEnd();
				}

				this.get_selected_node_line().dispatchEvent(new KeyboardEvent('keydown', {
				    code: 'Enter',
				    key: 'Enter',
				    charCode: 13,
				    keyCode: 13,
				    view: window,
				    bubbles: true
				}));

				paste =  paste.substring(new_line_pos + 1);
				new_line_pos = paste.search("\n");
			}
			
			let selection = window.getSelection();
			selection.deleteFromDocument();
			
			let textNode = document.createTextNode(paste);
			selection.getRangeAt(0).insertNode(textNode);
			selection.removeAllRanges();
			
			let range = document.createRange();
			range.selectNode(textNode);
			selection.addRange(range);
			selection.collapseToEnd();
			
		}
		e.preventDefault();
	}
}
